package SSF.App.DDoS;

import com.renesys.raceway.DML.*;
import com.renesys.raceway.SSF.*;
import java.util.*;
/**
 * This does NOT has many features in DDoSSessionRand, please use 
 * DDoSSessionRand instead unless the user wants to have full control of the 
 * attacking relay network.
 * <P>. In the configuration, the user needs to 
 * specify the whole relay network of attacker, masters and agents. It can be
 * a burden when one wants to use a lot of hijacked hosts.
 * <P>Notice: each host running it must have interface "0" connected.
 * @author Yougu Yuan
 * @see DDoSSessionRand
 */
public class DDoSSession extends ProtocolSession
{
    /** debug switch */
    private static boolean DEBUG;

    /** status constants, so are "INSTALLED" and "ATTACKING" */
    public static final int NOT_INSTALLED = -1;
    public static final int INSTALLED = 0;
    public static final int ATTACKING = 1;

    /** level constants, so are "MASTER" and "ZOMBIE"*/
    public static final int ATTACKER = 0;
    public static final int MASTER = 1;
    public static final int ZOMBIE = 2;

    /** virtual interface that is assumed to be on at every host*/
    public static final String VIRTUAL_INTERFACE = "(0)";

    /** attack method constant */
    public static final int SYN=0;

    /** status variable, default is "INSTALLED"*/
    public int status = INSTALLED;

    /** level variable, it shows where in the control tree this host locates.*/
    protected int level = ZOMBIE;

    /** attack starting time (second). It must be initialized for ATTACKER*/
    protected int startAttackTime = 100;

    /** attack ending time (second). It must be initialized for ATTACKER*/
    protected int endAttackTime = 200;

    /** target IP */
    protected int targetIP;

    /** target port*/
    protected int targetPort=80;

    /** attack method*/
    protected int attackMethod=SYN;

    /** parameter of attacking */
    protected int attackParam = 1;

    /** array of IP addresses of children that are directly under control 
	of this master or attacker
    */
    protected int[] children = null;

    /** IP on this host */
    protected IP ipSess=null;

    /** control tree config file */
    protected static String controlTreeFile=null;
    protected static Configuration controlTreeConfig=null;

    /** timer */
    protected Timer myTimer=null;

    /** random stream */
    protected static RandomStream rng=null;

    /** config the status and level of this DDoSSession instance */
    @Override
	public void config(Configuration cfg) throws configException
    {
	super.config(cfg);

	String evals = null;

	//config the static controlTreeFile if it's not configed yet.
	if (controlTreeFile == null) {
	    evals = (String)cfg.findSingle("debug");
	    if (evals != null) {
		if (evals.equals("true"))
		    DEBUG = true;
	    }

	    evals = (String)cfg.findSingle("control_tree_file");
	    if (evals == null) {
		System.err.println("control_tree_file attribute is not found for the DDoSSession.");
		throw new configException("control_tree_file attribute not found.");
	    }
	    else controlTreeFile = evals;

	    // get the configuration
	    dmlConfig controlConfig = new dmlConfig(controlTreeFile);
	    controlTreeConfig = 
		(Configuration)controlConfig.findSingle("control_tree");

	    if (controlTreeConfig == null) {
		System.err.println("Control Tree Configuration database must define a control_tree attribute.");
		System.err.println(controlConfig);
		throw new configException("control_tree attribute not found in the configuration file of control tree.");
	    }
	}

	//create randomstream if it's not there yet.
	if (rng == null) {
	    rng = Net.accessRandomStream(this, "DDoS");
	}
    }

    @Override
	public void init() throws ProtocolException
    {
	String evals = null;
	Configuration localCfg = null;
	//get local IP. IP must be configed by now, otherwise may be mistaken!!
	ipSess = (IP)(inGraph().SessionForName("ip"));

	//try to find itself in the control_tree_config file with nhi address. 
	//if it's not there, then turn itself to "NOT_INSTALLED"
	try {
	    localCfg = (Configuration)
		controlTreeConfig.findSingle((((Host)(inGraph()))).nhi);

	    if (localCfg == null) {
		return;  //it may be a zombie that doesn't need init.
	    }

	    //config status
	    evals = (String)localCfg.findSingle("status");
	    initStatus(evals);
	    
	    //config level 
	    evals = (String)localCfg.findSingle("level");
	    initLevel(evals);
	    
	    //config specific to attacker.
	    if (level == ATTACKER)
		initAttacker(localCfg);
	    else if (level == MASTER)     // config specific to masters
		initMaster(localCfg);
	    else initZombie(localCfg);  // config specific to zombies.
	} catch (configException ce) {
	    throw new ProtocolException("Error in initialization of DDoSSession");
	}
    }

    @Override
	public boolean push(ProtocolMessage message,
			ProtocolSession fromSession)
	throws ProtocolException
    {
	DDoSMessage msg = null;
	try {
	    msg = (DDoSMessage)message;
	} catch (ClassCastException cce) {
	    cce.printStackTrace();
	    throw new ProtocolException("Invalid message received in DDoSSession.push()");
	}

	// send back ACK msg if it's not an ACK itself
	if (msg.action != DDoSMessage.ACK) 
	    sendACK(msg);

	// execute command
	switch (msg.action) {
	case DDoSMessage.ACK:
	    break;
	case DDoSMessage.START_ATTACK:
	    if (status == INSTALLED)
		startAttack(msg);
	    else System.err.println("Warning: receive START_ATTACK cmd when status is not INSTALLED.   Host:"+((Host)inGraph()).nhi);
	    break;
	case DDoSMessage.END_ATTACK:
	    if (status == ATTACKING)
		endAttack(msg);
	    else System.err.println("Warning: receive END_ATTACK cmd when status is not ATTACKING.   Host:"+((Host)inGraph()).nhi);
	    break;
	case DDoSMessage.INSTALL:
	    if (status == NOT_INSTALLED)
		install(msg);
	    else System.err.println("Warning: receive INSTALL cmd when status is not NOT_INSTALLED.   Host:"+((Host)inGraph()).nhi);
	    break;
	case DDoSMessage.UNINSTALL:
	    if (status == ATTACKING)
		endAttack(msg);
	    if (status == INSTALLED)
		status = NOT_INSTALLED;
	    else System.err.println("Warning: receive NOT_INSTALL cmd when status is not ATTACKING or INSTALLED.  Host:"+((Host)inGraph()).nhi);
	    break;
	default: {
		System.err.println("Invalid command:"+ msg.action+" in DDoSSession.push()");
		throw new ProtocolException("Invalide command in DDoSMessage");
	    }
	}
	return true;
    }


    protected void install(DDoSMessage msg)
    {
	status = INSTALLED;
    }

    protected void sendACK(DDoSMessage msg) throws ProtocolException
    {
	DDoSMessage ackMsg = new DDoSMessage(DDoSMessage.ACK,0,0,msg.attackMethod,0);
	IpHeader ipHeader = new IpHeader(Protocols.TEST_PRTL_NUM,
					 ipSess.INTERFACE_SET[0].ipAddr,
					 ((IpHeader)(msg.previous())).SOURCE_IP);
	ipHeader.carryPayload(ackMsg);
	ipSess.push(ipHeader, this);
    }

    protected void startAttack(DDoSMessage msg)
    {
	status = ATTACKING;
	try {
	    switch (level) {
	    case ATTACKER:
		a_startAttack(msg);
		break;
	    case MASTER:
		m_startAttack(msg);
		break;
	    case ZOMBIE:
		z_startAttack(msg);
		break;
	    default: {
		System.err.println("Host "+ ((Host)inGraph()).nhi+": Invalid level in DDoSSession.startAttack()");
		status = INSTALLED;
		}
	    }
	} catch (ProtocolException pe) {
	    System.err.println(pe.getMessage());
	    pe.printStackTrace();
	}
    }

    /** attacker start attack */
    private  void a_startAttack(DDoSMessage msg) throws ProtocolException
    {
	//create attacking command package.
	if (DEBUG)
	    System.out.println("--*-- Attacker start attacking --*-- "+
			       "\n\ttargetIP:"+IP_s.IPtoString(targetIP)+
			       "\n\ttargetPort:"+targetPort+
			       "\n\tattackMethod:"+attackMethod+
			       "\n\tattackParam:"+attackParam);
	DDoSMessage outMsg = null;
	/*DDoSMessage outMsg = new DDoSMessage(DDoSMessage.START_ATTACK,
					     targetIP,
					     targetPort,
					     attackMethod,
					     attackParam);
	*/
	IpHeader tmpHeader=null;

	//send msgs to all the masters one by one.
	for (int i=0; i<children.length; i++) {
	    //create an IP header to use.
	    outMsg = new DDoSMessage(DDoSMessage.START_ATTACK,
				     targetIP,
				     targetPort,
				     attackMethod,
				     attackParam);
	    tmpHeader = new IpHeader(Protocols.TEST_PRTL_NUM,
				     ipSess.INTERFACE_SET[0].ipAddr,
				     children[i]);
	    tmpHeader.carryPayload(outMsg);
	    ipSess.push(tmpHeader, this);
	}

	//set timer to end attack
	myTimer = new Timer(inGraph(), SSF.Net.Net.seconds(endAttackTime-startAttackTime)) 
	    {
		@Override
		public void callback() {
		    endAttack(null);
		}
	    };
	myTimer.set();
    }

    /** master start attack*/
    private void m_startAttack(DDoSMessage msg) throws ProtocolException
    {
	if (DEBUG)
	    System.out.println("master "+((Host)inGraph()).nhi+" start attacking.");
	IpHeader tmpHeader;	
	for (int i=0; i<children.length; i++) {
	    tmpHeader = new IpHeader(Protocols.TEST_PRTL_NUM,
				     ipSess.INTERFACE_SET[0].ipAddr,
				     children[i]);
	    tmpHeader.carryPayload(new DDoSMessage(msg));
	    ipSess.push(tmpHeader, this);
	}
    }

    /** zombie start attack*/
    private void z_startAttack(DDoSMessage msg) throws ProtocolException
    {
	// get all parameters.
	targetIP = msg.targetIP;
	targetPort = msg.targetPort;
	attackParam = msg.param;
	attackMethod = msg.attackMethod;

	if (DEBUG)
	    System.out.println("zombie:"+((Host)inGraph()).nhi+
			       " intializing attacking.");

	if (attackMethod != SYN) {
	    System.err.println("attacking method other than \"SYN\" hasn't been implemented.");
	    throw new ProtocolException("attacking method not implemented.");
	}

	// start the timer for attacking. 
	myTimer = new Timer(inGraph(), 1) {
		@Override
		public void callback(){
		    zombieAttack();
		}
	    };
	myTimer.set(attackParam * SSF.Net.Net.seconds(1));
    }

    private void zombieAttack()
    {
	if (DEBUG)
	    System.out.println("Zombie:"+((Host)inGraph()).nhi+" is sending out attacking packet.");

	//create the TCP Header part.
	TCP_Header tcpHeader = new TCP_Header
	    ((new Double(rng.nextDouble())).intValue(), 
	     targetPort,0,0,30,TCP_Header.SYN);

	TCP_Message outMsg = new TCP_Message
	    (tcpHeader, (new Double(rng.nextDouble()*Integer.MAX_VALUE).intValue()), targetIP,1);


	try {
	    ipSess.push(outMsg.previous(), this);
	} catch (ProtocolException pe) {
	    pe.printStackTrace();
	    System.err.println(pe.getMessage());
	}

	myTimer.set(attackParam * SSF.Net.Net.seconds(1));
    }
    protected void endAttack(DDoSMessage msg)
    {
	//in this implementation, "msg" is not used.
	status = INSTALLED;
	try {
	    switch (level) {
	    case ATTACKER:
		a_endAttack(null);
		break;
	    case MASTER:
		m_endAttack(msg);
		break;
	    case ZOMBIE:
		z_endAttack(msg);
		break;
	    default:
		System.err.println("Host " + ((Host)inGraph()).nhi+ ": Invalid level in DDoSLevel.endAttack(). Level:"+level);
	    }
	} catch (ProtocolException pe) {
	    System.err.print(pe.getMessage());
	}
    }

    private void a_endAttack(DDoSMessage msg) throws ProtocolException
    {
	// stop the timer.
	if (DEBUG)
	    System.out.println("Attacker issue stop attacking command.");
	myTimer = null;

	DDoSMessage outMsg = new DDoSMessage(DDoSMessage.END_ATTACK,
					     targetIP,
					     targetPort,
					     attackMethod,
					     attackParam);
	IpHeader tmpHeader=null;

	//send msgs to all the masters one by one.
	for (int i=0; i<children.length; i++) {
	    //create an IP header to use.
	    tmpHeader = new IpHeader(Protocols.TEST_PRTL_NUM,
				     ipSess.INTERFACE_SET[0].ipAddr,
				     children[i]);
	    tmpHeader.carryPayload(outMsg);
	    ipSess.push(tmpHeader, this);
	}
	// send out stop message.

    }

    private void m_endAttack(DDoSMessage msg) throws ProtocolException
    {
	// just forward msg
	if (DEBUG)
	    System.out.println("Master "+ ((Host)inGraph()).nhi+" forwards stop attacking command.");
	IpHeader tmpHeader;
	for (int i=0; i<children.length; i++) {
	    tmpHeader = new IpHeader(Protocols.TEST_PRTL_NUM,
				     ipSess.INTERFACE_SET[0].ipAddr,
				     children[i]);
	    tmpHeader.carryPayload(new DDoSMessage(msg));
	    ipSess.push(tmpHeader, this);
	}
    }

    private void z_endAttack(DDoSMessage msg) throws ProtocolException
    {
	// stop the timer
	if (DEBUG)
	    System.out.println("Zombie:"+((Host)inGraph()).nhi+" stop attackingat "+inGraph().now()/(float)SSF.Net.Net.seconds(1));
	myTimer.cancel();
	myTimer = null;
    }

    protected void initStatus(String evals) throws configException
    {
    	if (evals != null) {
	    if (evals.equals("NOT_INSTALLED"))
		status = NOT_INSTALLED;
	    else if (evals.equals("INSTALLED"))
		status = INSTALLED;
	    else {
		System.err.println("Invalid \'status\':"+"evals"+" in the control_tree_file. (It can only be INSTALLED or NOT_INSTALLED)");
		throw new configException("Invalid status");
	    }
	    // by default it's "INSTALLED". 
	    // it can't be inited to be "ATTACKING"
	}
    }

    protected void initLevel(String evals) throws configException
    {
	if (evals != null) {
	    //default is ZOMBIE
	    if (evals.equals("ATTACKER"))
		level = ATTACKER;
	    else if (evals.equals("MASTER"))
		level = MASTER;
	    else if (evals.equals("ZOMBIE"))
		level = ZOMBIE;
	    else {
		System.err.println("Invalid \'level\':"+"evals"+" is given.\n It can only be ATTACKER, MASTER or ZOMBIE");
		throw new configException("Invalid level");
	    }
	}
    }

    protected void initAttacker(Configuration cfg) throws configException
    {
	String evals = null;

	if (DEBUG)
	    System.out.println("Init Attacker "+((Host)inGraph()).nhi+"\tIP:"
			       +IP_s.IPtoString(ipSess.INTERFACE_SET[0].ipAddr));

	//default is 100
	evals = (String)cfg.findSingle("start_attack_time");
	if (evals != null) 
	    startAttackTime = Integer.valueOf(evals).intValue();
	
	//default is 200
	evals = (String)cfg.findSingle("end_attack_time");
	if (evals != null)
	    endAttackTime = Integer.valueOf(evals).intValue();

	//get target ip
	evals = (String)cfg.findSingle("target_nhi");
	if (evals !=null) {
	    evals = ((Host)inGraph()).getNet().nhi_to_ip(evals+VIRTUAL_INTERFACE);
	    if (evals ==null) {
		System.err.println("Invalid target_nhi address.");
		throw new configException("Invalid target_nhi address.");
	    }
	    else targetIP = IP_s.StrToInt(evals);
	}
	else {
	    System.err.println("No target_ip specified for the attacker.");
	    throw new configException("No target_ip specified.");
	}

	//get port number, default is 80
	evals = (String)cfg.findSingle("target_port");
	if (evals != null)
	    targetPort = Integer.valueOf(evals).intValue();


	// get attack method
	evals = (String)cfg.findSingle("attack_method");
	if (evals != null) {
	    if (evals.equals("SYN"))
		attackMethod = SYN;
	    else {
		System.err.println("Attacking method other than \"SYN\" hasn't been implemented.");
		throw new configException("Attacking method not implemented.");
	    }
	}

	// get parameter if needed.
	if (attackMethod == SYN) {
	    evals = (String)cfg.findSingle("param");
	    if (evals != null)
		attackParam = Integer.valueOf(evals).intValue();
	}

	//construct a master list.
	LinkedList tempList = new LinkedList();
	for (Enumeration tempEnum = cfg.find("master");
	     tempEnum.hasMoreElements();) {
	    evals = ((String)tempEnum.nextElement()) + VIRTUAL_INTERFACE;;
	    // now the nhi of that master is in evals.
	    // convert it to ip address and store it in the tempList.

	    if (DEBUG)
		System.err.println("master:" + evals);
	    evals = ((Host)inGraph()).getNet().nhi_to_ip(evals);
	    if (DEBUG)
		System.err.println("\tIP:"+evals);

	    // convert it into an Integer.
	    tempList.add(new Integer(IP_s.StrToInt(evals)));
	}

	//convert the list of Integers into int[].
	int lSize = tempList.size();
	children = new int[lSize];
	for (int i = 0; i<lSize; i++)
	    children[i] = ((Integer)tempList.get(i)).intValue();

	//set a timer to fire at the startAttackTime
	myTimer = new Timer(inGraph(), SSF.Net.Net.seconds(startAttackTime)) {
		@Override
		public void callback() {
		    startAttack(null);
		}
	    };
	myTimer.set();
    }

    protected void initMaster(Configuration cfg) throws configException
    {
	if (DEBUG)
	    System.out.println("Init Master "+((Host)inGraph()).nhi+"\tIP:"
			       +IP_s.IPtoString(ipSess.INTERFACE_SET[0].ipAddr));
	//construct a master list.
	String evals = null;
	LinkedList tempList = new LinkedList();
	for (Enumeration tempEnum = cfg.find("zombie");
	     tempEnum.hasMoreElements();) {
	    evals = ((String)tempEnum.nextElement()) + VIRTUAL_INTERFACE;
	    // now the nhi of that zombie is in evals.
	    // convert it to ip address and store it in the tempList.
	    if (DEBUG)
		System.out.println("zombie: "+evals);

	    evals = ((Host)inGraph()).getNet().nhi_to_ip(evals);

	    if (DEBUG)
		System.out.println("\tIP: "+evals);
	    // convert it into an Integer.
	    tempList.add(new Integer(IP_s.StrToInt(evals)));
	}

	//convert the list of Integers into int[].
	int lSize = tempList.size();
	children = new int[lSize];
	for (int i = 0; i<lSize; i++)
	    children[i] = ((Integer)tempList.get(i)).intValue();

    }
    protected void initZombie(Configuration cfg) throws configException
    {
	//does nothing here in this kind of attack.
    }
}
